#include "buffer/lru_replacer.h"

#include <algorithm>
#include <list>
#include <memory>
#include <stdexcept>

namespace boltDB {
LRUKReplacer::LRUKReplacer(size_t num_frames, size_t k)
    : replacer_size_(num_frames), k_(k) {}

auto LRUKReplacer::Evict(frame_id_t* frame_id) -> bool {
  // 1.先检查访问次数小于K次的
  std::scoped_lock<std::mutex> locker(latch_);
  auto under_k_iterator =
      std::find_if(under_k_node_queue.begin(), under_k_node_queue.end(),
                   [](LRUKInfo::ptr& info) { return info->is_evictable_; });
  if (under_k_iterator != under_k_node_queue.end()) {
    *frame_id = (*under_k_iterator)->fid_;
    under_k_node_queue.erase(under_k_iterator);
    under_k_node_table_.erase(*frame_id);
    curr_size_--;
    return true;
  }

  auto cache_iterator =
      std::find_if(cache_.begin(), cache_.end(),
                   [](LRUKInfo::ptr& info) { return info->is_evictable_; });
  // 2.小于k次的没有就检查访问次数大于k次的
  if (cache_iterator != cache_.end()) {
    *frame_id = (*cache_iterator)->fid_;
    cache_.erase(cache_iterator);
    cached_node_table_.erase(*frame_id);
    curr_size_--;
    return true;
  }
  return false;
}

void LRUKReplacer::RecordAccess(frame_id_t frame_id) {
  std::scoped_lock<std::mutex> locker(latch_);
  // 1.检查大于k次的，有就访问，然后重新插入队列
  auto cache_table_iterator = cached_node_table_.find(frame_id);
  if (cache_table_iterator != cached_node_table_.end()) {
    // auto cache_node = std::move(*(cache_table_iterator->second));
    // cache_node->last_accuess_time_=current_timestamp_;
    // 从缓存队列中删除，然后再加入到缓存队列中
    // cache_.erase(cache_table_iterator->second);
    // cached_node_table_[frame_id]= Cache(std::move(cache_node));
    auto cache_node = (*cache_table_iterator->second).get();
    cache_node->last_accuess_time_ = current_timestamp_;
    cached_node_table_[frame_id] = CacheListModifyLocation(cache_table_iterator->second);
    current_timestamp_++;
  }
  // 2.检查小于k次的，有就增加记录，满足访问次数为k 就移入队列
  auto under_k_table_iterator = under_k_node_table_.find(frame_id);
  if (under_k_table_iterator != under_k_node_table_.end()) {
    auto under_k_node = std::move(*(under_k_table_iterator->second));
    under_k_node->last_accuess_time_ = current_timestamp_;
    under_k_node->access_count_++;
    current_timestamp_++;
    if (under_k_node->access_count_ >= k_) {
      under_k_node_queue.erase(under_k_table_iterator->second);
      cached_node_table_[frame_id] = Cache(std::move(under_k_node));
      return;
    }
  }

  // 3.如果都没有的话，就新建一个插入待缓存队列
  auto new_node = std::make_unique<LRUKInfo>(k_, frame_id);
  new_node->access_count_ = 1;
  new_node->last_accuess_time_ = current_timestamp_;
  under_k_node_queue.push_back(std::move(new_node));
  auto new_node_it = under_k_node_queue.end();
  new_node_it--;
  under_k_node_table_.emplace(frame_id, new_node_it);
  current_timestamp_++;
}

void LRUKReplacer::SetEvictable(frame_id_t frame_id, bool set_evictable) {
  std::scoped_lock<std::mutex> locker(latch_);
  auto under_k_table_iterator = under_k_node_table_.find(frame_id);
  auto cache_table_iterator = cached_node_table_.find(frame_id);
  if (under_k_table_iterator == under_k_node_table_.end() &&
      cache_table_iterator == cached_node_table_.end()) {
    throw std::invalid_argument("The frame Id is not found");
  }
  auto node_it = under_k_table_iterator != under_k_node_table_.end()
                     ? under_k_table_iterator->second
                     : cache_table_iterator->second;
  auto node = node_it->get();
  if (node->is_evictable_ && !set_evictable) {
    curr_size_--;
  } else if (!node->is_evictable_ && set_evictable) {
    curr_size_++;
  }
  node->is_evictable_ = set_evictable;
}

void LRUKReplacer::Remove(frame_id_t frame_id) {
  std::scoped_lock<std::mutex> locker(latch_);
  // 1.检查大于k次的，有就删除
  auto cache_table_iterator = cached_node_table_.find(frame_id);
  if (cache_table_iterator != cached_node_table_.end()) {
    cache_.erase(cache_table_iterator->second);
    cached_node_table_.erase(cache_table_iterator);
    curr_size_--;
    return;
  }
  // 2.检查小于k次的，有就删除，满足访问次数为k 就移出队列
  auto under_k_table_iterator = under_k_node_table_.find(frame_id);
  if (under_k_table_iterator != under_k_node_table_.end()) {
    under_k_node_queue.erase(under_k_table_iterator->second);
    under_k_node_table_.erase(under_k_table_iterator);
    curr_size_--;
  }
}

auto LRUKReplacer::Size() -> size_t { return curr_size_; }

auto LRUKReplacer::Cache(LRUKInfo::ptr cache_node)
    -> std::list<LRUKInfo::ptr>::iterator {
  if (cache_node == nullptr) {
    throw std::invalid_argument("ptr is null");
  }

  for (auto it = cache_.begin(); it != cache_.end(); it++) {
    if ((*it)->GetKDistance(current_timestamp_) <
        cache_node->GetKDistance(current_timestamp_)) {
      // 在当前节点前插入新节点，完成插入，退出函数。
      return cache_.insert(it, std::move(cache_node));
    }
  }
  cache_.push_back(std::move(cache_node));
  return --cache_.end();
}

auto LRUKReplacer::CacheListModifyLocation(
    LRUKReplacer::LRUKInfo_location old_location)
    -> std::list<LRUKInfo::ptr>::iterator {
  if (old_location == cache_.end()) {
    throw std::invalid_argument("cache_node is invalid");
  }
  auto node = std::move(*old_location);
  auto next = old_location;
  ++next;
  cache_.erase(old_location);

  for (auto it = next; it != cache_.end(); it++) {
    if ((*it)->GetKDistance(current_timestamp_) <
        node->GetKDistance(current_timestamp_)) {
      // 在当前节点前插入新节点，完成插入，退出函数
      return cache_.insert(it, std::move(node));
    }
  }
  cache_.push_back(std::move(node));
  // 返回最后一个元素的位置
  return --cache_.end();
}

auto LRUKReplacer::LRUKInfo::GetKDistance(size_t now) -> int32_t {
  if (access_count_ < k_) {
    return -1;
  }
  return now - last_accuess_time_;
}

}  // namespace boltDB